rm -rf remains
Just for fun, I decided to launch a new Linux server and run rm -rf /
as root to see what remains. As I found out, rm
lives in the future with idiots like me, so you have to specify --no-preserve-root
to kick this exercise off.
# rm -rf --no-preserve-root /
After committing this act of tomfoolery, great utilities like
/bin/ls
/bin/cat
/bin/chmod
/usr/bin/file
will all be gone! You should still have your connection over SSH as well as your existing bash session. This means you have all the bash builtins, like echo
.
Becoming Bash McGyver
root@rmrf:/# ls
-bash: /bin/ls: No such file or directory
There is no ls
, but echo
and fileglobs are still around. What can we do with those?
root@rmrf:/# echo *
dev proc run sys
# echo /dev/pts/*
/dev/pts/0 /dev/pts/3 /dev/pts/ptmx
Hey, we got to keep /dev
, /proc
, /run
, and /sys
. Now that we have ls
, we might as well make it a little easier to read.
root@rmrf:/# for file in /dev/pts/*; do echo $file; done
/dev/pts/0
/dev/pts/3
/dev/pts/ptmx
Several Redditors pointed out that printf
is available.
root@rmrf:/# ls() { printf '%s\n' ${1:+${1%/}/}*; }
"printf will [apply] the format string until it runs out of args." - camh-
Since you can define functions in bash, we can create an ls
utility, albeit very limited.
root@rmrf:/# ls() { printf '%s\n' ${1:+${1%/}/}*; }
-bash: syntax error near unexpected token `('
What? That should be completely valid. Is ls
already hashed or aliased?
root@rmrf:/# type ls
ls is aliased to `ls --color=auto'
Ah, that gets expanded to ls--color=auto () { printf '%s\n' ${1:+${1%/}/}*; }
. Yuck. Well, we can unalias
that.
root@rmrf:/# unalias ls
root@rmrf:/# ls() { printf '%s\n' ${1:+${1%/}/}*; }
root@rmrf:/# ls
/dev
/proc
/run
/sys
root@rmrf:/# ls /dev
/dev/pts
And save our work.
root@rmrf:/# echo 'ls() { printf '%s\n' ${1:+${1%/}/}*; }' >> utils.sh
root@rmrf:/# source utils.sh
How about cat
? The read
builtin comes in handy along with pipes and redirection, so we can piece together a rudimentary cat
.
root@rmrf:/# (while read line; do echo "$line"; done) < utils.sh
ls() { printf '%s\n' ${1:+${1%/}/}*; }
With these abilities and the fact that we can write arbitrary bytes with echo
, we could rebuild and then curl
or wget
the binaries we want directly. My first choice, echoed by others, would be to get busybox. Busybox is the Swiss Army Knife of Embedded Linux, with builtin versions of wget
, dd
, tar
, and many others. Eusebeîa goes into great detail about how to get a fully escaped version of busybox on your system, so I won't do that here.
There is a problem though.
Even if we echo
all the bytes we need into creating entire binaries, those files won't be executable. No way to start busybox. The easiest workaround for this is to find something which is executable and overwrite it with echo
. We've nuked all of /usr
and /bin
at this point though, so that's a bit tricky.
We can use shell globs and bash logic to find files with the executable bit set, making sure to ignore directories.
executable () { if [[ ( ! -d $1 ) && -x $1 ]] ; then echo "$1"; fi }
Find the executables!
root@rmrf:/# for file in /*; do executable $file; done
root@rmrf:/# for file in /*/*; do executable $file; done
root@rmrf:/# for file in /*/*/*; do executable $file; done
/proc/1107/exe
/proc/1136/exe
/proc/1149/exe
/proc/1179/exe
/proc/1215/exe
/proc/1217/exe
/proc/1220/exe
/proc/1221/exe
/proc/1223/exe
/proc/1248/exe
/proc/1277/exe
/proc/1468/exe
/proc/1478/exe
/proc/1625/exe
/proc/1644/exe
/proc/1/exe
/proc/374/exe
/proc/378/exe
/proc/471/exe
/proc/616/exe
/proc/657/exe
/proc/self/exe
Great! Hold on a minute though, those are all symbolic links to executables that no longer exist on disk. We'll just update executable()
to ignore symbolic links.
root@rmrf:/# executable () { if [[ ( ! -d $1 ) && ( ! -h $1 ) && -x $1 ]] ; then echo "$1"; fi }
root@rmrf:/# for file in /*/*/*; do executable $file; done
root@rmrf:/# for file in /*/*/*/*; do executable $file; done
root@rmrf:/# for file in /*/*/*/*/*; do executable $file; done
root@rmrf:/# for file in /*/*/*/*/*/*; do executable $file; done
Well now, that's bad news bears. Perhaps there is something at the kernel level we can use. After all, we can restart the box using sysrq magic:
root@rmrf:/# echo 1 > /proc/sys/kernel/sysrq
root@rmrf:/# echo "b" > /proc/sysrq-trigger
Now we're locked out and I should do something else on a Friday. Thanks for reading! Let me know if you figure out how to get an executable bit set.
UPDATE: Redditor throw_away5046 posted a full, beautiful, solution to this.
From a non-hosed, same architecture box:
$ mkdir $(xxd -p -l 16 /dev/urandom)
$ cd $_
$ apt-get download busybox-static
$ dpkg -x *.deb .
$ alias encode='{ tr -d \\n | sed "s#\\(..\\)#\\\\x\\1#g"; echo; }'
$ alias upload='{ xxd -p | encode | nc -q0 -lp 5050; }'
$ upload < bin/busybox
Back on the rmrf'ed machine
# cd /
# alias decode='while read -ru9 line; do printf "$line"; done'
# alias download='( exec 9<>/dev/tcp/{IP OF NON HOSED BOX}/5050; decode )'
# download > busybox
Now to create a shared object that will change permissions on busybox
$ cat > setx.c <<EOF
extern int chmod(const char *pathname, unsigned int mode);
int entry(void) {
return !! chmod("busybox", 0700);
}
char *desc[] = {0};
struct quick_hack {
char *name; int (*fn)(void); int on;
char **long_doc, *short_doc, *other;
} setx_struct = { "setx", entry, 1, desc, "chmod 0700 busybox", 0 };
EOF
$ gcc -Wall -Wextra -pedantic -nostdlib -Os -fpic -shared setx.c -o setx
$ upload < setx
Time to enable
setx as a built in and get busybox executable
# ( download > setx; enable -f ./setx setx; setx; )
# /busybox mkdir .bin
# /busybox --install -s .bin
# PATH=/.bin
In action:
For all my blog posts I've decided to hold discussion on Reddit, linking to the post. Today's post is on /r/linux and on /r/programming, but feel free to cross post it. PM me if you want me to link it here.
Go forth and build your bash knowledge. Personally, I recommend Unix Power Tools.
Through college and career, flipping through this book has always showed me something new.